Skip to content

Conversation

@dabeibao
Copy link

The escape sequence is:
ESC[N q

N is between 0 and 6.

The escape sequence is:
    ESC[N q

N is between 0 and 6.
@achernya
Copy link
Collaborator

What is the context of this?

@dabeibao
Copy link
Author

dabeibao commented Oct 17, 2025

Please see the link:
https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797?permalink_comment_id=5165100#gistcomment-5165100

Many modern terminals (including Windows Terminal, mintty, etc.) support changing the cursor shape dynamically — and this pairs beautifully with Emacs Evil mode, so you can visually distinguish between Normal and Insert modes (like in Vim).

Normal mode:
图片

Insert mode:
图片

@achernya
Copy link
Collaborator

Is there any more authoritative documentation of these escape sequences?

@dabeibao
Copy link
Author

What about this: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html

Search 'CSI Ps SP q' in the page:

CSI Ps SP q
          Set cursor style (DECSCUSR), VT520.
            Ps = 0  ⇒  blinking block.
            Ps = 1  ⇒  blinking block (default).
            Ps = 2  ⇒  steady block.
            Ps = 3  ⇒  blinking underline.
            Ps = 4  ⇒  steady underline.
            Ps = 5  ⇒  blinking bar, xterm.
            Ps = 6  ⇒  steady bar, xterm.

@achimnol
Copy link

achimnol commented Nov 5, 2025

We have long standing PRs and issues for this feature.

/* cursor shape */
static void CSI_cursorshape( Framebuffer* fb, Dispatcher* dispatch )
{
int shape = dispatch->getparam( 0, -1 );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at the impl for Dispather::getparam and it looks like doesn't support returning values less than 1. But 0 is a valid cursor shape.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. The defaultval should be 0.

@dabeibao
Copy link
Author

* [Apply Clang formatting to #1167 and resolve merge conflicts #1292](https://github.com/mobile-shell/mosh/pull/1292)

Seems this commit already implements it. But the title is somewhat misleading...

@eminence
Copy link
Member

Something's still not quite right when the terminal is reset. Try this:

printf '\x1b[6 q\x1bc'

@dabeibao
Copy link
Author

dabeibao commented Dec 6, 2025

Something's still not quite right when the terminal is reset. Try this:

printf '\x1b[6 q\x1bc'

Do you mean it also needs to reset the cursor shape after a terminal reset?
I tried Windows Terminal (without mosh), and seems it doesn’t reset the cursor shape either.

@eminence
Copy link
Member

eminence commented Dec 6, 2025

You're right that Windows Terminal doesn't reset the cursor shape after reset. But I tested xterm, gnome-terminal, alacritty, and ghostty, and they all reset cursor shape after reset. So I think we should too.

@dabeibao
Copy link
Author

dabeibao commented Dec 6, 2025

I have tried this in both mintty, xterm, gnome-terminal and alacritty (in WSL though).
These terminals reset the cursor shape after \x1bc, both with and without mosh.
But for window terminal, it never resets the shape even without mosh.

So it seems that this behavior depends on the terminal itself?

Which terminal do you see different behavior with mosh? I can have a try with it.

@achernya
Copy link
Collaborator

achernya commented Dec 6, 2025

mosh claims to be xterm and therefore should act like xterm.

@eminence
Copy link
Member

eminence commented Dec 6, 2025

@dabeibao I'm not seeing that. Here's a more detailed screenshot showing my test (using commit 4ead896)

Running printf '\x1b[6 q' shows a steady bar cursor, as expected:

image

But then running printf '\x1bc or reset still shows the bar cursor:

image

But when I do the above steps in a non-mosh terminal, the cursor resets back to a blinking block cursor (the default)

@eminence
Copy link
Member

eminence commented Dec 6, 2025

The problem seems to be that the cursor shape is initialized to -1 when the terminal is initialized or reset. But a value of -1 will not be synchronized with the client, so after a reset, the server has shape=-1 but the client has shape=6

  if ( f.ds.cursor_shape != frame.last_frame.ds.cursor_shape && f.ds.cursor_shape != -1 ) {
    snprintf(tmp, sizeof(tmp), "\033[%d q", f.ds.cursor_shape);
    frame.append(tmp);
  }

I think I would just get rid of the init to -1 and init to 0 instead

@dabeibao
Copy link
Author

dabeibao commented Dec 7, 2025

The problem seems to be that the cursor shape is initialized to -1 when the terminal is initialized or reset. But a value of -1 will not be synchronized with the client, so after a reset, the server has shape=-1 but the client has shape=6

  if ( f.ds.cursor_shape != frame.last_frame.ds.cursor_shape && f.ds.cursor_shape != -1 ) {
    snprintf(tmp, sizeof(tmp), "\033[%d q", f.ds.cursor_shape);
    frame.append(tmp);
  }

I think I would just get rid of the init to -1 and init to 0 instead

Yes, thanks for finding this.
I now understand why I couldn’t reproduce it. I was using a private commit 6a533d6 (which includes other changes like cursor color and strike-through, so I didn’t push it to this PR yet).
It uses a counter initialized to 0, so it occasionally bypasses this bug. 😊

I tried to initialize cursor_shape to 0. But it still has some bug:
After reset:
Without mosh: steady block.
With the fix: blinking block.

In xterm, the default cursor_shape is 'steady block'. And without mosh, after reset it is still 'steady block'.
But if we initialize cursor_shape to 0, after reset mosh will detect the change and send a '\033[0 q', which changes the cursor shape to 'blink block'.

@dabeibao
Copy link
Author

dabeibao commented Dec 7, 2025

I tried to investigate further. Seems the problem is in the reset (\033c) itself.
Without mosh, when xterm receives the \033c seq, it calls VTReset then ReallyReset to reset cursor blink:

#if OPT_BLINK_CURS
    screen->cursor_blink_esc = 0;
    SetCursorBlink(xw, screen->cursor_blink_i);
    TRACE(("cursor_shape:%d blinks:%d\n",
	   screen->cursor_shape,
	   screen->cursor_blink));
#endif

screen->cursor_blink_esc = 0; will clear the blink state previously set by escape sequence.

However, it appears that mosh consumes the reset seq and doesn't forward it to the terminal.
After modifying the code to forward the reset seq and I did see the cursor reset to a steady block in xterm. However it also caused unexepcted behavior (e.g. the shell prompt being rendered as blank spaces).

Another issue with xterm (so as gnome-terminal) is, the initial cursor shape is configurable. User can set both the shape and blink state. Thus mosh cannot assume the initial cursor_shape value.

So from my perspective, we may have the approaches:

  1. Set default cursor_shape to -1
    After reset we keep cursor shape. If users never change the cursor shape, they will never see any differences.

  2. Set default cursor_shape to 2
    This matches xterm's default configuration. But it could override/break user customized settings after reset sequence.

I personally prefer option 1 as it will not impact the users who don't need this sequence.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants